package com.zmc.lostfound.common.utils.redis;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zmc.lostfound.common.constant.RedisConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * @author ZMC
 * @description Redis缓存工具类
 */
@Slf4j
@Component
public class RedisCacheUtil {

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    private final StringRedisTemplate stringRedisTemplate;

    private final RedisDistributedLock redisDistributedLock;

    public RedisCacheUtil(StringRedisTemplate stringRedisTemplate, RedisDistributedLock redisDistributedLock) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.redisDistributedLock = redisDistributedLock;
    }

    /**
     * 获取随机数
     *
     * @param randomTTL 是否随机设置TTL
     */
    private int getRandomInt(boolean randomTTL) {
        // 随机获取[0,1000]的正整数
        return randomTTL ? RandomUtil.randomInt(0, 1000) + 1 : 0;
    }

    /**
     * 向redis缓存进行写请求: 插入任意Java类型的数据，使用redis的String数据类型存储，并设置TTL
     * TTL: 缓存有效期，过期后缓存自动删除
     * 随机设置TTL: 使缓存的TTL尽可能均匀，可以在一定程度上避免缓存雪崩
     *
     * @param key       数据的key
     * @param value     任意类型的数据
     * @param time      过期时间
     * @param unit      时间单位
     * @param randomTTL 是否随机设置TTL
     */
    public <VALUE> void setStringWithTTL(String key, VALUE value, Long time, TimeUnit unit, boolean randomTTL) {
        int randomInt = getRandomInt(randomTTL);
        // 手动序列化: JSONUtil.toJsonStr(value)
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), randomInt + time, unit);
    }

    public <VALUE> void setStringWithTTL(String key, VALUE value, Long time, TimeUnit unit) {
        this.setStringWithTTL(key, value, time, unit, false);
    }

    public <VALUE> void setString(String key, VALUE value) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value));
    }

    /**
     * 缓存预热(缓存重建)，并设置TTL
     *
     * @param keyPrefix  key的前缀
     * @param id         数据库表的主键id(或unique字段)
     * @param dbFallback 查询MySQL数据库的方法
     * @param time       过期时间
     * @param unit       时间单位
     */
    public <ENTITY, ID> boolean cacheWarmUpWithTTL(String keyPrefix, ID id, Function<ID, ENTITY> dbFallback, Long time, TimeUnit unit, boolean randomTTL) {
        // 1.查询数据库中的数据
        ENTITY entity = dbFallback.apply(id);
        if (entity == null) {
            return false;
        }
        // 2.写入redis
        try {
            this.setStringWithTTL(keyPrefix + id, entity, time, unit, randomTTL);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public <ENTITY, ID> boolean cacheWarmUpWithTTL(String keyPrefix, ID id, Function<ID, ENTITY> dbFallback, Long time, TimeUnit unit) {
        return this.cacheWarmUpWithTTL(keyPrefix, id, dbFallback, time, unit, false);
    }


    /**
     * 向redis缓存进行写请求: 插入任意Java类型的数据，使用redis的String数据类型存储，并设置逻辑过期时间
     *
     * @param key   数据的key
     * @param value 数据
     * @param time  逻辑过期时间
     * @param unit  时间单位
     */
    public <VALUE> void setStringWithLogicalExpire(String key, VALUE value, Long time, TimeUnit unit) {
        RedisData redisData = new RedisData();
        redisData.setData(value);
        // 设置逻辑过期时间: redis缓存里的TTL数值单位为s，因此逻辑过期时间单位也使用s
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        // 手动序列化: JSONUtil.toJsonStr(redisData)
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }


    /**
     * 缓存预热(缓存重建)，并设置逻辑过期
     *
     * @param keyPrefix  key的前缀
     * @param id         数据库表的主键id(或unique字段)
     * @param dbFallback 查询MySQL数据库的方法
     * @param time       逻辑过期时间
     * @param unit       时间单位
     */
    public <ENTITY, ID> boolean cacheWarmUpWithLogicalExpire(String keyPrefix, ID id, Function<ID, ENTITY> dbFallback, Long time, TimeUnit unit) {
        // 1.查询数据库中的数据
        ENTITY entity = dbFallback.apply(id);
        if (entity == null) {
            return false;
        }
        // 2.封装逻辑过期时间，写入redis
        try {
            this.setStringWithLogicalExpire(keyPrefix + id, entity, time, unit);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向redis缓存进行写请求: 插入Map<String, Object>类型的数据，使用redis的Hash数据类型存储，并设置TTL
     * TTL: 缓存有效期，过期后缓存自动删除
     * 随机设置TTL: 使缓存的TTL尽可能均匀，可以在一定程度上避免缓存雪崩
     *
     * @param key       数据的key
     * @param value     Map<String, Object>类型的数据
     * @param time      过期时间
     * @param unit      时间单位
     * @param randomTTL 是否随机设置TTL
     */
    public void setMapWithTTL(String key, Map<String, Object> value, Long time, TimeUnit unit, boolean randomTTL) {
        int randomInt = getRandomInt(randomTTL);
        // 1.先存储
        stringRedisTemplate.opsForHash().putAll(key, value);
        // 2.再设置TTL
        stringRedisTemplate.expire(key, randomInt + time, unit);
    }

    public void setMapWithTTL(String key, Map<String, Object> value, Long time, TimeUnit unit) {
        this.setMapWithTTL(key, value, time, unit, false);
    }

    public void setMap(String key, Map<String, Object> value) {
        stringRedisTemplate.opsForHash().putAll(key, value);
    }

    /**
     * 向redis缓存进行写请求: 插入List<String>类型的数据，使用redis的List数据类型存储，并设置TTL
     * TTL: 缓存有效期，过期后缓存自动删除
     * 随机设置TTL: 使缓存的TTL尽可能均匀，可以在一定程度上避免缓存雪崩
     *
     * @param key       数据的key
     * @param value     List<String>类型的数据
     * @param time      过期时间
     * @param unit      时间单位
     * @param randomTTL 是否随机设置TTL
     */
    public <T> void setListWithTTL(String key, List<T> value, Long time, TimeUnit unit, boolean randomTTL) {
        int randomInt = getRandomInt(randomTTL);
        // 1.先存储
        this.setList(key, value);
        // 2.再设置TTL
        stringRedisTemplate.expire(key, randomInt + time, unit);
    }

    public <T> void setListWithTTL(String key, List<T> value, Long time, TimeUnit unit) {
        this.setListWithTTL(key, value, time, unit, false);
    }

    public <T> void setList(String key, List<T> value) {
        List<String> stringValue = new ArrayList<>();
        for (T t : value) {
            stringValue.add(JSONUtil.toJsonStr(t));
        }
        stringRedisTemplate.opsForList().rightPushAll(key, stringValue);
    }

    /**
     * 判断value是否在Key对应的set集合中
     *
     * @param key   set集合的key
     * @param value set集合的元素之一
     * @return 判断结果
     */
    public <T> Boolean isMemberSet(String key, T value) {
        return stringRedisTemplate.opsForSet().isMember(key, JSONUtil.toJsonStr(value));
    }

    /**
     * 将value添加到Key对应的set集合中
     *
     * @param key       set集合的key
     * @param value     set集合的元素之一
     * @param time      过期时间
     * @param unit      时间单位
     * @param randomTTL 是否随机TTL
     */
    public <T> void addSetWithTTL(String key, T value, Long time, TimeUnit unit, boolean randomTTL) {
        int randomInt = getRandomInt(randomTTL);
        // 1.先存储
        stringRedisTemplate.opsForSet().add(key, JSONUtil.toJsonStr(value));
        // 2.再设置TTL
        stringRedisTemplate.expire(key, randomInt + time, unit);
    }

    public <T> void addSetWithTTL(String key, T value, Long time, TimeUnit unit) {
        this.addSetWithTTL(key, value, time, unit, false);
    }

    public <T> void addSet(String key, T value) {
        // 手动序列化: JSONUtil.toJsonStr(value)
        stringRedisTemplate.opsForSet().add(key, JSONUtil.toJsonStr(value));
    }

    /**
     * 将value从set集合中移除
     *
     * @param key   set集合的key
     * @param value set集合的元素之一
     */
    public <T> void removeSet(String key, T value) {
        stringRedisTemplate.opsForSet().remove(key, JSONUtil.toJsonStr(value));
    }

    /**
     * 求两个set集合的交集
     *
     * @param key1 set集合的key
     * @param key2 set集合的key
     * @return 交集
     */
    public Set<String> intersect(String key1, String key2) {
        return stringRedisTemplate.opsForSet().intersect(key1, key2);
    }

    /**
     * 求两个set集合的并集
     *
     * @param key1 set集合的key
     * @param key2 set集合的key
     * @return 并集
     */
    public Set<String> union(String key1, String key2) {
        return stringRedisTemplate.opsForSet().union(key1, key2);
    }

    /**
     * 求两个set集合的差集
     *
     * @param key1 set集合的key
     * @param key2 set集合的key
     * @return 差集: key1 - key2
     */
    public Set<String> difference(String key1, String key2) {
        return stringRedisTemplate.opsForSet().difference(key1, key2);
    }

    /**
     * 查询set集合中的所有元素
     *
     * @param key set集合的key
     * @return set集合所有元素
     */
    public Set<String> getSetAll(String key) {
        return stringRedisTemplate.opsForSet().members(key);
    }

    /**
     * 查询Key对应的SortedSet集合中的value元素的分数
     * 如果SortedSet集合中存在value元素，则返回分数
     * 如果value元素不存在，则返回null
     *
     * @param key   SortedSet集合的key
     * @param value SortedSet集合的元素之一
     * @return 判断结果
     */
    public <T> Double getScoreForSortedSet(String key, T value) {
        return stringRedisTemplate.opsForZSet().score(key, JSONUtil.toJsonStr(value));
    }

    /**
     * 将value添加到Key对应的SortedSet集合中
     *
     * @param key       SortedSet集合的key
     * @param value     SortedSet集合的元素之一
     * @param time      过期时间
     * @param unit      时间单位
     * @param randomTTL 是否随机TTL
     */
    public <T> void addSortedSetWithTTL(String key, T value, double score, Long time, TimeUnit unit, boolean randomTTL) {
        int randomInt = getRandomInt(randomTTL);
        // 1.先存储
        stringRedisTemplate.opsForZSet().add(key, JSONUtil.toJsonStr(value), score);
        // 2.再设置TTL
        stringRedisTemplate.expire(key, randomInt + time, unit);
    }

    public <T> void addSortedSetWithTTL(String key, T value, double score, Long time, TimeUnit unit) {
        this.addSortedSetWithTTL(key, value, score, time, unit, false);
    }

    public <T> void addSortedSet(String key, T value, double score) {
        // 手动序列化: JSONUtil.toJsonStr(value)
        stringRedisTemplate.opsForZSet().add(key, JSONUtil.toJsonStr(value), score);
    }

    /**
     * 将value从SortedSet集合中移除
     *
     * @param key   SortedSet集合的key
     * @param value SortedSet集合的元素之一
     */
    public <T> void removeSortedSet(String key, T value) {
        stringRedisTemplate.opsForZSet().remove(key, JSONUtil.toJsonStr(value));
    }

    /**
     * 查询SortedSet集合中[min, max]的元素(SortedSet集合默认是按score从小到大排序的)
     *
     * @param key SortedSet集合的key
     * @param min 边界值
     * @param max 边界值
     * @return 查询结果集
     */
    public Set<String> rangeSortedSet(String key, long min, long max) {
        return stringRedisTemplate.opsForZSet().range(key, min, max);
    }

    /**
     * 查询SortedSet集合中[min, max]的元素(按score从大到小排序)
     *
     * @param key SortedSet集合的key
     * @param min 边界值
     * @param max 边界值
     * @return 查询结果集
     */
    public Set<String> reverseRangeSortedSet(String key, long min, long max) {
        return stringRedisTemplate.opsForZSet().reverseRange(key, min, max);
    }

    /**
     * 按key删除redis缓存
     *
     * @param key 要删除的数据的key
     */
    public void deleteByKey(String key) {
        stringRedisTemplate.delete(key);
    }

    /**
     * 向redis缓存进行读请求: 查询单个数据(单个K-V键值对)
     * 通过缓存空字符串解决缓存穿透问题
     * 通过设置随机TTL解决缓存雪崩问题
     *
     * @param keyPrefix  key的前缀
     * @param id         数据库表的主键id(或unique字段)
     * @param type       实体类的Class(查询结果的Java类型)
     * @param dbFallback 查询MySQL数据库的方法
     * @param time       过期时间
     * @param unit       时间单位
     */
    public <ENTITY, ID> ENTITY queryWithPassThrough(String keyPrefix, ID id, Class<ENTITY> type, Function<ID, ENTITY> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.查询redis缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 判断json是否是空串(因为采用缓存空字符串解决缓存穿透问题)
        if ("".equals(json)) {
            // 返回错误信息
            return null;
        }
        // 2.判断缓存是否命中，并且是合理数据
        if (StrUtil.isNotBlank(json)) {
            // 缓存命中，手动反序列化，返回查询结果
            return JSONUtil.toBean(json, type);
        }
        // 3.缓存未命中，根据id查询MySQL数据库
        ENTITY entity = dbFallback.apply(id);
        // 4.MySQL数据库中不存在
        if (entity == null) {
            // 缓存空字符串，设置TTL为3分钟: 解决缓存穿透的问题
            stringRedisTemplate.opsForValue().set(key, "", RedisConstant.CACHE_NULL_TTL, TimeUnit.MINUTES);
            // 返回错误信息
            return null;
        }
        // 5.MySQL数据库存在，写入redis缓存，并设置随机TTL
        this.setStringWithTTL(key, entity, time, unit, true);
        // 6.返回查询结果
        return entity;
    }

    /**
     * 向redis缓存进行读请求: 查询单个数据(单个K-V键值对)
     * 通过缓存空字符串解决缓存穿透问题
     * 通过设置随机TTL解决缓存雪崩问题
     * 利用互斥锁解决缓存击穿问题(热点Key问题)
     *
     * @param valueKeyPrefix 数据key的前缀
     * @param lockKeyPrefix  互斥锁key的前缀
     * @param id             数据库表的主键id(或unique字段)
     * @param type           实体类的Class(查询结果的Java类型)
     * @param dbFallback     查询MySQL数据库的方法
     * @param time           过期时间
     * @param unit           时间单位
     * @return 查询结果
     */
    public <ENTITY, ID> ENTITY queryWithMutex(String valueKeyPrefix, String lockKeyPrefix, Class<ENTITY> type, ID id, Function<ID, ENTITY> dbFallback, Long time, TimeUnit unit) {
        String valueKey = valueKeyPrefix + id;
        String lockKey = lockKeyPrefix + id;
        boolean tryLock = false;
        while (true) {
            // 1.从redis缓存中查询
            String json = stringRedisTemplate.opsForValue().get(valueKey);
            // 判断json是否是空串(因为采用缓存空字符串解决缓存穿透问题)
            if ("".equals(json)) {
                // 返回错误信息
                return null;
            }
            // 2.缓存命中，返回查询结果
            if (StrUtil.isNotBlank(json)) {
                //System.err.println("=============================缓存已由其它线程重建，查询成功，不需要获取锁============================");
                // 缓存命中，手动反序列化，返回查询结果
                return JSONUtil.toBean(json, type);
            }
            // 3.未命中，准备缓存重建(利用互斥锁解决缓存击穿问题)
            try {
                // 3.1尝试获取互斥锁(如果获取成功，设置锁的TTL为10s，只要比缓存重建的业务时间长一点即可)
                tryLock = redisDistributedLock.tryLock(lockKey, 10L, TimeUnit.SECONDS);
                // 3.2获取失败，线程休眠，重试(自旋锁)
                if (!tryLock) {
                    //System.err.println("=============================获取锁失败============================");
                    // 线程休眠50ms，避免一直尝试获取锁
                    Thread.sleep(50);
                    // 重试
                    continue;
                }
                // 3.3获取成功
                //System.err.println("=============================获取锁成功成功成功成功成功============================");
                // 3.4需要再次检测redis缓存是否存在商户信息，因为其它线程可能已经完成了缓存重建，那么本线程就不需要缓存重建
                json = stringRedisTemplate.opsForValue().get(valueKey);
                // 判断json是否是空串(因为采用缓存空字符串解决缓存穿透问题)
                if ("".equals(json)) {
                    // 返回错误信息
                    return null;
                }
                // 缓存命中，无需缓存重建，返回查询结果
                if (StrUtil.isNotBlank(json)) {
                    //System.err.println("=============================缓存已由其它线程重建，查询成功，不需要获取锁============================");
                    // 缓存命中，手动反序列化，返回查询结果
                    return JSONUtil.toBean(json, type);
                }
                // 3.5缓存未命中，说明本线程是第一个获取到锁的线程，应该由本线程完成缓存重建
                // 模拟缓存重建的延时: 200ms
                //Thread.sleep(200);
                // 4.根据id查询数据库
                ENTITY entity = dbFallback.apply(id);
                // 4.1数据库中不存在
                if (entity == null) {
                    // 缓存空字符串，设置TTL为3分钟: 解决缓存穿透的问题
                    stringRedisTemplate.opsForValue().set(valueKey, "", RedisConstant.CACHE_NULL_TTL, TimeUnit.MINUTES);
                    // 返回错误信息
                    return null;
                }
                // 4.2数据库中存在，写入redis缓存，并设置随机TTL
                this.setStringWithTTL(valueKey, entity, time, unit, true);
                // 返回查询结果
                return entity;
            } catch (InterruptedException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            } finally {
                // 除非在 try 块、catch 块中调用了退出虚拟机的方法System.exit(int status)，否则不管在 try 块或者 catch 块中执行怎样的代码，出现怎样的情况，异常处理的 finally 块总会执行。
                // 4.3释放互斥锁(删除互斥锁KV键值对)
                if (tryLock) {
                    redisDistributedLock.unLockWithLua(lockKey);
                    //System.err.println("=============================缓存重建完成，释放锁成功成功成功成功成功============================");
                }
            }
        }
    }

    /**
     * 利用逻辑过期解决缓存击穿问题
     *
     * @param valueKeyPrefix 数据key的前缀
     * @param lockKeyPrefix  互斥锁key的前缀
     * @param id             数据库表的主键id(或unique字段)
     * @param type           实体类的Class(查询结果的Java类型)
     * @param dbFallback     查询MySQL数据库的方法
     * @param time           逻辑过期时间
     * @param unit           时间单位
     * @return 查询结果
     */
    public <ENTITY, ID> ENTITY queryWithLogicalExpire(String valueKeyPrefix, String lockKeyPrefix, Class<ENTITY> type, ID id, Function<ID, ENTITY> dbFallback, Long time, TimeUnit unit) {
        String valueKey = valueKeyPrefix + id;
        // 1.从redis缓存查询商户信息
        String json = stringRedisTemplate.opsForValue().get(valueKey);
        // 2.缓存未命中，说明所查询的数据不是热点Key，直接返回null
        if (StrUtil.isBlank(json)) {
            return null;
        }
        // 3.缓存命中，反序列化
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        ENTITY entity = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        // 4.逻辑时间未过期，直接返回查询结果
        if (expireTime.isAfter(LocalDateTime.now())) {
            return entity;
        }
        // 5.逻辑时间已过期，需要缓存重建，更新redis缓存的旧数据
        String lockKey = lockKeyPrefix + id;
        // 5.1尝试获取互斥锁(如果获取成功，设置锁的TTL为10s，只要比缓存重建的业务时间长一点即可)
        boolean tryLock = redisDistributedLock.tryLock(lockKey, 10L, TimeUnit.SECONDS);
        // 5.2获取锁成功
        if (tryLock) {
            // 5.3需要再次检测redis缓存是否逻辑过期，因为其它线程可能已经完成了缓存重建，那么本线程就不需要缓存重建
            json = stringRedisTemplate.opsForValue().get(valueKey);
            redisData = JSONUtil.toBean(json, RedisData.class);
            entity = JSONUtil.toBean((JSONObject) redisData.getData(), type);
            expireTime = redisData.getExpireTime();
            // 5.4逻辑时间未过期，直接返回查询结果
            if (expireTime.isAfter(LocalDateTime.now())) {
                return entity;
            }
            // 5.5逻辑时间依旧过期，说明本线程是第一个获取到锁的线程，需要由本线程开启独立线程完成缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    // 模拟缓存重建的延时: 200ms
                    //Thread.sleep(200);
                    // 缓存重建
                    this.cacheWarmUpWithLogicalExpire(valueKeyPrefix, id, dbFallback, time, unit);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                } finally {
                    // 释放互斥锁
                    redisDistributedLock.unLockWithLua(lockKey);
                }
            });
            // 5.5本线程直接返回过期的旧数据(所以会造成短期数据不一致)
            return entity;
        }
        // 5.6 获取锁失败，说明有其它线程正则执行缓存重建，本线程直接返回过期的旧数据(所以会造成短期数据不一致)
        return entity;
    }

    /**
     * 向redis缓存进行读请求: 查询全部数据
     *
     * @param listKey    redis的list数据结构的key
     * @param type       实体类的Class(查询结果的Java类型)
     * @param dbFallback 查询MySQL数据库的方法
     * @param param      方法参数
     * @param time       过期时间
     * @param unit       时间单位
     */
    public <ENTITY, PARAM> List<ENTITY> queryList(String listKey, Class<ENTITY> type, PARAM param, Function<PARAM, List<ENTITY>> dbFallback, Long time, TimeUnit unit) {
        // 1.查询redis缓存
        List<String> jsonList = stringRedisTemplate.opsForList().range(listKey, 0, -1);
        List<ENTITY> entityList = new ArrayList<>();
        // 2.缓存命中
        if (jsonList != null && !jsonList.isEmpty()) {
            for (String json : jsonList) {
                // 3.手动反序列化
                entityList.add(JSONUtil.toBean(json, type));
            }
            // 4.返回查询结果
            return entityList;
        }
        // 5.缓存未命中，查询MySQL数据库
        entityList = dbFallback.apply(param);
        // 6.MySQL数据库中不存在
        if (entityList == null || entityList.isEmpty()) {
            // 返回错误信息
            return null;
        }
        // 7.MySQL数据库存在，写入redis缓存
        this.setListWithTTL(listKey, entityList, time, unit);
        // 8.返回查询结果
        return entityList;
    }

    /**
     * 简单读请求: 查询单个数据(单个K-V键值对)
     *
     * @param key  数据的key
     * @param type 查询结果的Java对象的类型
     */
    public <ENTITY> ENTITY simpleMapQuery(String key, Class<ENTITY> type) {
        Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(key);
        if (map.isEmpty()) {
            return null;
        }
        return BeanUtil.mapToBean(map, type, false, new CopyOptions());
    }

    /**
     * 设置(刷新)key的过期时间
     *
     * @param key  数据的key
     * @param time 过期时间
     * @param unit 时间单位
     */
    public void expireKey(String key, Long time, TimeUnit unit) {
        stringRedisTemplate.expire(key, time, unit);
    }


}